﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UlsStudio.Engine;
using UlsStudio.Engine.SqlParsers;

namespace UlsStudio.AppCore
{
    public class GridDataset
    {
        #region Constructors & Privates

        object listViewCacheLock = new object();
        private readonly Dictionary<int, DatabaseLoadedUlsLogEntry> _listViewCache = null;
        private readonly ApplicationEngine _appCore = null;

        

        private bool _backgroundGridLoaderRunning = false;
        private static object backgroundLoadStatusLock = new object();
        int _highestViewIndexWanted = 0;
        private int? _lastSelectedID = 0;

        // Cache objects
        private static object cacheLock = new object();
        const int LOAD_SECTION_SIZE = 100;          // How many records to load at once per section-load
        const int LOAD_CACHE_BUFFER_LOW_MAX = 100;      // Difference between highest cache index & so-far-requested grid-view index before triggering a new section-load

        public GridDataset(ApplicationEngine appCore)
        {
            this._appCore = appCore;
            this._listViewCache = new Dictionary<int, DatabaseLoadedUlsLogEntry>();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Last selected ID by user on grid
        /// </summary>
        public int? LastSelectedID
        {
            get { return _lastSelectedID; }
            set
            {
                Logging.Writeline("GridDataset.LastSelectedID={0}", value);
                _lastSelectedID = value;
            }
        }

        public int HighestViewIndexWanted
        {
            get { return _highestViewIndexWanted; }
            set { _highestViewIndexWanted = value; }
        }

        /// <summary>
        /// ListView item cache
        /// </summary>
        public Dictionary<int, DatabaseLoadedUlsLogEntry> ListViewCache
        {
            get
            {
                lock (listViewCacheLock)
                {
                    return _listViewCache;
                }
            }
        }

        public ApplicationEngine AppCore
        {
            get { return _appCore; }
        } 

        /// <summary>
        /// A background load is in progress (to know whether to start a background load)
        /// </summary>
        public bool BackgroundGridLoaderRunning
        {
            get { lock (backgroundLoadStatusLock) { return _backgroundGridLoaderRunning; } }
            set { lock (backgroundLoadStatusLock) { _backgroundGridLoaderRunning = value; } }
        }

        #endregion 

        #region Event Definitions

        public delegate void NewRecordSelectedDelegate(AbstractUlsLogEntry e);
        public event NewRecordSelectedDelegate NewRecordSelected;
        private void OnNewRecordSelected(AbstractUlsLogEntry e)
        {
            if (NewRecordSelected != null) NewRecordSelected(e);
        }

        public delegate void WantedLogEntryLoadedDelegate(AbstractUlsLogEntry e);
        public event WantedLogEntryLoadedDelegate WantedLogEntryLoaded;
        private void OnWantedLogEntryLoaded(AbstractUlsLogEntry e)
        {
            if (WantedLogEntryLoaded != null) WantedLogEntryLoaded(e);
        }

        #endregion

        #region Cache Methods

        /// <summary>
        /// Does the view-cache have a specific record ID?
        /// </summary>
        private bool CacheHasDbId(int id, out DatabaseLoadedUlsLogEntry wantedEntry)
        {
            lock (cacheLock)
            {
                foreach (DatabaseLoadedUlsLogEntry entry in this.ListViewCache.Values)
                {
                    if (entry.DatabaseID == id)
                    {
                        wantedEntry = entry;
                        return true;
                    }
                }
            }
            wantedEntry = null;
            return false;
        }

        /// <summary>
        /// Get a list-view-item from cache
        /// </summary>
        public DatabaseLoadedUlsLogEntry GetLogEntryFromDisplayCache(DataLoadInfo loadInfo)
        {
            int startIndex = loadInfo.Index;

            // Do we need to populate cache 1st?
            bool haveEntryInCache = false;
            lock (cacheLock)
            {
                haveEntryInCache = ListViewCache.ContainsKey(startIndex);
            }
            if (!haveEntryInCache)
            {
                // Figure out what we want from the DB
                int fromRow = startIndex;       // Database ID is always +1 the row ID
                int toRow = fromRow + LOAD_SECTION_SIZE;

                // Beng it in cache + some more
                PopulateCache(fromRow, toRow, loadInfo.ViewQuery);
            }

            // Figure out how close the highest view-index is to cache-limit
            if (startIndex > this.HighestViewIndexWanted)
            {
                this.HighestViewIndexWanted = startIndex;

                lock (cacheLock)
                {
                    // Get highest requested index so far
                    int biggestCachedIndex = 1;
                    if (ListViewCache.Count > 0)
                    {
                        biggestCachedIndex = ListViewCache.Keys.Max();
                    }

                    // Do we need to spawn a new background load of the next section?
                    bool cachedItemsNearLimit = (biggestCachedIndex - this.HighestViewIndexWanted < LOAD_CACHE_BUFFER_LOW_MAX);
                    if (!this.BackgroundGridLoaderRunning && cachedItemsNearLimit)
                    {
                        if (biggestCachedIndex > LOAD_SECTION_SIZE)
                        {
                            this.BackgroundGridLoaderRunning = true;
                            Logging.Writeline("GridDataset.GetLogEntryFromDisplayCache - buffer-limit hit; highestViewIndexWanted={0}, biggestCachedIndex={1}", this.HighestViewIndexWanted, biggestCachedIndex);
                            System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(BackgroundLoadNextSection));
                            t.Start(startIndex);
                        }
                    }
                }

            }

            // Recheck again just in case of a cache-clear
            lock (cacheLock)
            {
                if (ListViewCache.ContainsKey(startIndex))
                {
                    return ListViewCache[startIndex];
                }
                else
                {
                    Logging.Writeline("FAILED to return object from cache for index {0}", startIndex);
                    throw new ApplicationException();
                }
            }
        }

        /// <summary>
        /// Populate cache from the highest view-index
        /// </summary>
        private void PopulateCache(int recordCount, SimpleSqlSelectStatementInfo query)
        {
            int biggestCachedIndex = 0;
            lock (cacheLock)
            {
                if (ListViewCache.Count > 0)
                {
                    biggestCachedIndex = ListViewCache.Keys.Max();
                }
            }
            PopulateCache(biggestCachedIndex, biggestCachedIndex + recordCount, query);
        }

        /// <summary>
        /// Range-populate view-cache
        /// </summary>
        private void PopulateCache(int fromRow, int toRow, SimpleSqlSelectStatementInfo query)
        {
            // Get range-selected items
            List<DatabaseLoadedUlsLogEntry> wantedItems = this.AppCore.UlsData.GetRangeSelectItems(fromRow, toRow, query);

            // Add to cache
            int readCount = 0;
            foreach (DatabaseLoadedUlsLogEntry loadedLogEntry in wantedItems)
            {
                // Define the view-index of each item section-loaded
                int viewIndex = fromRow + readCount;

                // Update indexen
                loadedLogEntry.ViewIndex = viewIndex;

                lock (cacheLock)
                {
                    if (!ListViewCache.ContainsKey(viewIndex))
                        ListViewCache.Add(viewIndex, loadedLogEntry);   // Row ID is always -1 the index ID  
                }

                // Goto last selected record if found
                if (this.LastSelectedID != -1 && loadedLogEntry.DatabaseID == this.LastSelectedID)
                {
                    Logging.Writeline("GridDataset.PopulateCache - Found last selected ID ({0})", this.LastSelectedID);
                    OnWantedLogEntryLoaded(loadedLogEntry);
                }

                readCount++;
            }
            Logging.Writeline("GridDataset.PopulateCache() Requested items '{0}' to '{1}' - {2} items read from DB", fromRow, toRow, readCount);
        }


        /// <summary>
        /// Load into view-cache and return a specific record ID
        /// </summary>
        public DatabaseLoadedUlsLogEntry GetLogEntryByIdFromDisplayCache(int id, SimpleSqlSelectStatementInfo query)
        {
            DatabaseLoadedUlsLogEntry wantedEntry = null;

            // See if the item is already in cache
            if (!CacheHasDbId(id, out wantedEntry))
            {
                // We haven't cached the item yet - load it
                if (wantedEntry == null)
                {
                    // Check we can still get the ID
                    if (this.AppCore.UlsData.ResultHaveId(query, id))
                    {
                        // Get where in the view-query results this ID will be
                        int wantedIdIndex = this.AppCore.UlsData.GetIdIndex(query, id);

                        // Load into cache the surounding section
                        PopulateCache(wantedIdIndex - 50, wantedIdIndex + 50, query);
                    }
                    else
                    {
                        throw new ApplicationException("Previous selected ID no longer available in query");
                    }

                    // Have in cache?
                    CacheHasDbId(id, out wantedEntry);
                }
                Logging.Writeline("GridDataset.GetLogEntryFromDisplayCache - record id '{0}' returned from db & added to view-cache.", id);
            }
            else
            {
                Logging.Writeline("GridDataset.GetLogEntryFromDisplayCache - record id '{0}' returned from view-cache without loading.", id);
            }

            return wantedEntry;
        }

        #endregion

        #region Background Loader
        /// <summary>
        /// Adds to ListViewCache the next section, starting at highest current ID
        /// </summary>
        private void BackgroundLoadNextSection(DataLoadInfo loadInfo)
        {
            int startIndex = loadInfo.Index;

            lock (cacheLock)
            {
                // Find first non-cached ID
                while (this.ListViewCache.Keys.Contains(loadInfo.Index))
                {
                    startIndex++;
                }
            }
            int fromRow = startIndex + 1, toRow = startIndex + LOAD_SECTION_SIZE;
            Logging.Writeline("GridDataset.BackgroundLoadNextSection - Loading from {0} to {1}", fromRow, toRow);

            // Get range-selected items
            List<DatabaseLoadedUlsLogEntry> wantedItems = this.AppCore.UlsData.GetRangeSelectItems(fromRow, toRow, loadInfo.ViewQuery);

            // Add to cache
            int readCount = 0;
            foreach (DatabaseLoadedUlsLogEntry loadedLogEntry in wantedItems)
            {
                // Define the view-index of each item section-loaded
                int viewIndex = fromRow + readCount;

                lock (cacheLock)
                {
                    if (!this.ListViewCache.ContainsKey(viewIndex))
                        this.ListViewCache.Add(viewIndex, loadedLogEntry);   // Row ID is always -1 the index ID  
                }

                readCount++;
            }

            lock (cacheLock)
            {
                this.BackgroundGridLoaderRunning = false;
            }
            Logging.Writeline("GridDataset.BackgroundLoadNextSection - Done loading {0} to {1}", fromRow, toRow);
        }
        private void BackgroundLoadNextSection(object loadInfo)
        {
            if (loadInfo is DataLoadInfo)
            {
                BackgroundLoadNextSection((DataLoadInfo)loadInfo);
            }
            
        }
        #endregion

        #region Selected Item Loader

        public void LoadClickedItem(object loadInfo) 
        {
            if (loadInfo is DataLoadInfo)
            {
                LoadClickedItem((DataLoadInfo)loadInfo); 
            }
            else
            {
                throw new ArgumentOutOfRangeException("loadInfo");
            }
            
        }
        public void LoadClickedItem(DataLoadInfo loadInfo)
        {
            int listIndex = loadInfo.Index;

            AbstractUlsLogEntry selectedItem = null; string msg = string.Empty;
            try
            {
                selectedItem = this.GetLogEntryFromDisplayCache(new DataLoadInfo(listIndex, loadInfo.ViewQuery));
                msg = selectedItem.Message;

                //if (this.EnableGridConsoleOut)
                //{

                //    Logging.Writeline("lstLogs_SelectionChanged selectedItem.DatabaseID={0}, sending msg '{1}'",
                //        ((DatabaseLoadedUlsLogEntry)selectedItem).DatabaseID, msg);
                //}
            }
            catch (ApplicationException)
            {
                // Ignore - something was requested that was deleted before it could be returned
            }

            // GUI
            OnNewRecordSelected(selectedItem);
            //this.Invoke(new Action<AbstractUlsLogEntry>(FinshLoadClickedItem), selectedItem);
        }

        #endregion 

        public void ResetCache()
        {
            Logging.Writeline("GridDataset.ResetCache - Cache reset.");
            this.HighestViewIndexWanted = 0;
            lock (cacheLock)
            {
                this.ListViewCache.Clear();
            }
        }
    }
}
